use anyhow::{Error, Result};
use base64::Engine;
use std::time::Duration;
use tokio::select;
use validator::ValidationError;

pub fn uuid_validator(str: &String) -> Result<(), ValidationError> {
    if uuid::Uuid::parse_str(str.to_string().as_str()).is_ok() {
        Ok(())
    } else {
        Err(ValidationError::new("invalid_uuid"))
    }
}

pub fn base64_validator(str: &String) -> Result<(), ValidationError> {
    if base64::prelude::BASE64_STANDARD
        .decode(str.as_bytes())
        .is_ok()
    {
        Ok(())
    } else {
        Err(ValidationError::new("invalid_base64"))
    }
}

pub fn cert_validator(ca_pem: &[u8], cert_pem: &[u8], key_pem: &[u8]) -> Result<()> {
    let ca = openssl::x509::X509::from_pem(ca_pem)?;
    let cert = openssl::x509::X509::from_pem(cert_pem)?;
    let key = openssl::pkey::PKey::private_key_from_pem(key_pem)?;
    // 校验cert和key是否匹配
    if !key.public_eq(cert.public_key()?.as_ref()) {
        return Err(Error::msg("key and certificate does not match"));
    }
    // 校验cert是否由ca签名
    if !cert.verify(ca.public_key()?.as_ref())? {
        return Err(Error::msg("certificate does not match"));
    }

    // 校验是否过期
    let now = openssl::asn1::Asn1Time::days_from_now(0)?;
    if now.gt(cert.not_after())
        || now.lt(cert.not_before())
        || now.gt(ca.not_after())
        || now.lt(ca.not_before())
    {
        return Err(Error::msg("certificate out of date"));
    }
    Ok(())
}

#[test]
fn test() {
    let mut e = Error::msg("1");
    e = e.context("2");
    e = e.context("3");
    let mut index = 0;
    for i in e.chain() {
        println!("{} : {:?}", index, i.to_string());
        index += 1;
    }

    println!("||| {}", e.root_cause());
}

#[tokio::test]
async fn test_async() {
    let (send, mut recv) = tokio::sync::broadcast::channel::<u8>(1);
    let s = tokio::spawn(async move {
        for i in 0..2 {
            select! {
                _ = tokio::time::sleep(Duration::from_secs(1)) => {
                    println!("sleep 1");
                }
                _ = recv.recv() => {
                    println!("recv 1");
                }
            }
        }
    });
    tokio::time::sleep(Duration::from_millis(500)).await;
    send.send(1).unwrap();
    s.await.unwrap();
}
